package net.luohuasheng.bee.proxy.mybatis.executor.processor;

import net.luohuasheng.bee.jdbc.tools.JdbcBuilder;
import net.luohuasheng.bee.jdbc.tools.common.utils.ReflectionUtils;
import net.luohuasheng.bee.jdbc.tools.component.DialectManager;
import net.luohuasheng.bee.jdbc.tools.component.execute.BaseExecuteDialect;
import net.luohuasheng.bee.jdbc.tools.component.execute.dto.BatchInsertDto;
import net.luohuasheng.bee.proxy.core.utils.CastUtils;
import net.luohuasheng.bee.proxy.core.utils.convert.ValueConvertUtils;
import net.luohuasheng.bee.proxy.core.utils.jpa.JpaModelUtils;
import net.luohuasheng.bee.proxy.core.utils.jpa.annotation.TableLogic;
import net.luohuasheng.bee.proxy.core.utils.jpa.dto.JpaColumnDto;
import net.luohuasheng.bee.proxy.core.utils.jpa.dto.JpaTableDto;
import net.luohuasheng.bee.proxy.jdbc.common.enums.DriverType;
import net.luohuasheng.bee.proxy.mybatis.common.exception.MybatisException;
import net.luohuasheng.bee.proxy.mybatis.common.utils.CrudUtils;
import net.luohuasheng.bee.proxy.mybatis.common.utils.MapperUtils;
import org.apache.ibatis.executor.CachingExecutor;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.jdbc.SQL;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.transaction.jdbc.JdbcTransaction;

import javax.sql.DataSource;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static net.luohuasheng.bee.proxy.mybatis.executor.intercetor.BaseInterceptor.Index.MAPPED_STATEMENT_INDEX;
import static net.luohuasheng.bee.proxy.mybatis.executor.intercetor.BaseInterceptor.LANGUAGE_DRIVER;

/**
 * @author luohuasheng
 */
public class InsertBatchProcessor extends BaseProcessor {
    public static final String INSERT_BATCH = "INSERT BATCH";

    public InsertBatchProcessor(Configuration configuration, Class<?> entityClass) {
        super(configuration, entityClass);
    }

    @Override
    public void rewrite(Invocation invocation, MappedStatement ms, BoundSql boundSql, Object parameter) throws Exception {
        String sql = boundSql.getSql();
        List<?> list = CastUtils.cast(((Map<?, ?>) parameter).get(BATCH_LIST));
        Class<?> emptyClass = null;
        if (list == null || list.isEmpty()) {
            throw new MybatisException("empty list");
        } else {
            String className = sql.replace(InsertBatchProcessor.INSERT_BATCH, "").trim();
            emptyClass = CrudUtils.class.getClassLoader().loadClass(className);
        }
        JpaTableDto jpaTableDto = JpaModelUtils.getJpaTable(emptyClass);
        for (Object value : list) {
            if (value == null) {
                continue;
            }
            for (JpaColumnDto column : jpaTableDto.getColumns()) {
                if (column.getTableLogic() != null) {
                    TableLogic logic = column.getTableLogic();
                    column.getField().setAccessible(true);
                    column.getField().set(value, ValueConvertUtils.convert(logic.noDelFlag(), column.getField().getType()));
                    column.getField().setAccessible(false);

                }
            }
        }

        DataSource dataSource = CrudUtils.dataSource(invocation);
        DriverType driverType = JdbcBuilder.create().getDriverType(dataSource);
        BaseExecuteDialect baseExecuteDialect = DialectManager.getExecuteDialect(driverType);
        SQL insertInto = new SQL().INSERT_INTO(jpaTableDto.getName());
        List<JpaColumnDto> columns = jpaTableDto.getColumns().stream().filter(JpaColumnDto::isInsertable).collect(Collectors.toList());
        Set<String> fields = new LinkedHashSet<>();
        String[] columnNames = new String[columns.size()];
        for (int i = 0; i < columns.size(); i++) {
            JpaColumnDto column = columns.get(i);
            fields.add(column.getField().getName());
            columnNames[i] = column.getName();
        }
        insertInto.INTO_COLUMNS(columnNames);
        BatchInsertDto batchInsertDto = (baseExecuteDialect.buildBatchInsert(fields, false));
        sql = insertInto.toString() + (batchInsertDto.getContent().trim().toLowerCase().startsWith("select") ? " " : " values ") + String.format(FOREACH_DEFAULT, batchInsertDto.getItem(), batchInsertDto.getIndex() == null ? "index" : batchInsertDto.getIndex(), BATCH_LIST, batchInsertDto.getOpen(), batchInsertDto.getSeparator(), batchInsertDto.getClose(), batchInsertDto.getContent());
        SqlSource sqlSource = LANGUAGE_DRIVER.createSqlSource(ms.getConfiguration(), "<script>\n\t" + sql + "</script>", null);
        BoundSql newBoundSql = sqlSource.getBoundSql(parameter);
        invocation.getArgs()[MAPPED_STATEMENT_INDEX.getAnInt()] = MapperUtils.copyFromNewSql(ms, newBoundSql);
    }

    @Override
    public SqlSource createSqlSource() {
        return createSqlSource(INSERT_BATCH + " " + entityClass.getName());
    }

    @Override
    public KeyGenerator keyGenerator() {
        return getKey().getKeyGenerator();
    }

    @Override
    public String keyProperty() {
        return getKey().getKeyProperty();
    }

    @Override
    public String keyColumn() {
        return getKey().getKeyProperty();
    }
}
